import Watcher from './watcher';

export default class Compiler {
  constructor(vm) {
    this.vm = vm;
    this.el = vm.$el;
    this.compile(this.el);
  }
  compile(el) {
    const childNodes = el.childNodes;
    Array.from(childNodes).forEach(node => {
      if (this.isTextNode(node)) {
        this.compileText(node);
      } else if (this.isElementNode(node)) {
        this.compileElement(node);
      }
      this.compile(node);
    });
  }
  compileText(node) {
    const content = node.textContent;
    const reg = /\{\{(.+)\}\}/;
    if (reg.test(content)) {
      const key = RegExp.$1.trim();
      node.textContent = content.replace(reg, this.vm[key]);

      new Watcher(this.vm, key, newValue => {
        node.textContent = content.replace(reg, newValue);
      });
    }
  }
  compileElement(node) {
    Array.from(node.attributes).forEach(attr => {
      let attrName = attr.name;
      if (this.isDirective(attrName)) {
        if (attrName.includes('on:')) {
          attrName = attrName.substring(5);
          this.onUpdater(node, attr.value, attrName);
        } else {
          attrName = attrName.substring(2);
          this.update(node, attr.value, attrName);
        }
      }
    });
  }
  update(node, key, attrName) {
    const updateFn = this[`${attrName}Updater`];
    updateFn && updateFn.call(this, node, this.vm[key], key);
  }
  textUpdater(node, value, key) {
    node.textContent = value;
    new Watcher(this.vm, key, newValue => {
      node.textContent = newValue;
    });
  }
  modelUpdater(node, value, key) {
    node.value = value;
    new Watcher(this.vm, key, newValue => {
      node.value = newValue;
    });

    node.addEventListener('input', () => {
      this.vm[key] = node.value;
    });
  }
  htmlUpdater(node, value, key) {
    node.innerHTML = value;
    new Watcher(this.vm, key, newValue => {
      node.innerHTML = newValue;
    });
  }
  onUpdater(node, fn, event) {
    const reg = /\[(.+)\]/;
    if (reg.test(event)) {
      event = this.vm[RegExp.$1];
    }
    let eve = event.match(/\.(.+)/g);

    if (eve !== null) {
      eve = eve[0];
      event = event.replace(eve, '');
    }

    node.addEventListener(event, e => {
      // v-on:click.stop  阻止冒泡
      if (eve && eve.includes('.stop')) {
        e.stopPropagation();
      }
      // v-on:click.prevent 阻止默认事件
      if (eve && eve.includes('.prevent')) {
        e.preventDefault();
      }
      // 如果没有在methods上面绑定方法，给出警告
      if (
        this.vm.$options.methods === undefined ||
        typeof this.vm.$options.methods[fn] !== 'function'
      ) {
        console.warn('v-on instruction requires a function');
        return;
      }
      this.vm.$options.methods[fn]();
    });
  }
  isDirective(attrName) {
    return attrName.startsWith('v-');
  }
  isTextNode(node) {
    return node.nodeType === 3;
  }
  isElementNode(node) {
    return node.nodeType === 1;
  }
}
